/*
 * completion.c -- readline completion functions
 *
 * Yet Another FTP Client
 * Copyright (C) 1998-2001, Martin Hedenfalk <mhe@stacken.kth.se>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version. See COPYING for more details.
 */

#include "syshdr.h"
#include "strq.h"
#include "alias.h"
#include "commands.h"
#include "input.h"
#include "gvars.h"
#include "set.h"
#include "cmd.h"
#include "url.h"

cpl_t force_completion_type = cpUnset;

#ifdef HAVE_LIBREADLINE

static bool remote_dir_only = false;

char *no_completion_function(const char *text, int state)
{
	return 0;
}

static char *alias_completion_function(const char *text, int state)
{
    static int len;
	alias *ap;
	static listitem *lip = 0;

	if(!gvAliases)
		return 0;

    if(!state) {
		len = strlen(text);
		lip = gvAliases->first;
	}

    while(lip) {
		ap = (alias *)lip->data;
		lip = lip->next;
		if(strncmp(ap->name, text, len) == 0)
			return xstrdup(ap->name);
    }
    return 0;
}

static char *bookmark_complete_internal(const char *text, int state,
										bool skip_domains)
{
    static int len;
	url_t *url;
	static listitem *lip = 0;

	if(!gvBookmarks)
		return 0;

    if(!state) {
		len = strlen(text);
		lip = gvBookmarks->first;
	}

    while(lip) {
		url = (url_t *)lip->data;
		lip = lip->next;
		if(skip_domains && url->hostname[0] == '.')
			continue;
		if(url->alias) {
			if(strncmp(url->alias, text, len) == 0)
				return xstrdup(url->alias);
		} else if(strncmp(url->hostname, text, len) == 0)
				return xstrdup(url->hostname);
    }
    return 0;
}

static char *bookmark_completion_function(const char *text, int state)
{
	return bookmark_complete_internal(text, state, false);
}


static char *hostname_completion_function(const char *text, int state)
{
	return bookmark_complete_internal(text, state, true);
}

/* Generator function for command completion.  STATE lets us know whether
 * to start from scratch; without any state (i.e. STATE == 0), then we
 * start at the top of the list.
 */
static char *command_completion_function(const char *text, int state)
{
    static int list_index, len;
	static int alias_state;
    char *e;

    if(!state) {
		list_index = 0;
		len = strlen(text);
		alias_state = 0;
    }

    /* Return the next name which partially matches from the command list. */
    while((e = cmds[list_index].cmd) != 0) {
		list_index++;
		if(strncasecmp(e, text, len) == 0)
			return xstrdup(e);
    }
	e = alias_completion_function(text, alias_state);
	alias_state = 1;
	return e;
}

static char *remote_completion_function(const char *text, int state)
{
  static int len;            /* length of unquoted */
  static char *dir = NULL;   /* any initial directory in text */
  static char *unquoted = NULL; /* the unquoted filename (or beginning of it) */
  static listitem *lip = NULL;
  static rdirectory *rdir = NULL; /* the cached remote directory */
  static char merge_fmt[] = "%s/%s";

  if (!ftp_loggedin())
    return 0;

  /* this is not really true, this is for local filename completion,
   * but it works here too (sort of), and it looks nicer, since
   * the whole path is not printed by readline, ie
   * only foo is printed and not /bar/fu/foo (if cwd == /bar/fu)
   * readline appends a class character (ie /,@,*) in _local_ filenames
   */
  rl_filename_completion_desired = 1;
#ifndef HAVE_LIBEDIT
  rl_filename_quoting_desired = 1;
#endif

  if (!state) {
    dir = base_dir_xptr(text);
    if (dir) {
      stripslash(dir);
      char* e = strchr(dir, 0);
      if (e[-1]=='\"')
        e[-1] = '\0';
      unquote(dir);
      if (strcmp(dir, "/") == 0)
        strlcpy(merge_fmt, "%s%s", sizeof(merge_fmt));
      else
        strlcpy(merge_fmt, "%s/%s", sizeof(merge_fmt));
    }
#ifndef HAVE_LIBEDIT
    if(gvWaitingDots) {
      rl_insert_text("..."); /* show dots while waiting, like ncftp */
      rl_redisplay();
    }
#endif

    char* ap = ftp_path_absolute(dir);
    rdir = ftp_cache_get_directory(ap);
    const bool dir_is_cached = (rdir != 0);
    if (!rdir)
      rdir = ftp_read_directory(ap);
    free(ap);

#ifndef HAVE_LIBEDIT
    if (gvWaitingDots)
      rl_do_undo(); /* remove the dots */
#endif

    if (!dir_is_cached && ftp_get_verbosity() >= vbCommand)
      rl_forced_update_display();

    if (!rdir) {
      free(dir);
      return 0;
    }
    unquoted = dequote_filename(base_name_ptr(text), 0);
    if (!unquoted)
      unquoted = (char *)xmalloc(1);
    len = strlen(unquoted);
    lip = rdir->files->first;
  }

  while (lip) {
    rfile* fp = (rfile *)lip->data;
    lip = lip->next;

    /* 0 = not dir, 1 = dir, 2 = link (maybe dir) */
    const int isdir = ftp_maybe_isdir(fp);
    if (remote_dir_only && isdir == 0)
      continue;

    const char* name = base_name_ptr(fp->path);
    /* skip dotdirs in completion */
    if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
      continue;

    if (strncmp(name, unquoted, len) == 0) {
      char *ret;
      if (dir)
      {
        if (asprintf(&ret, merge_fmt, dir, name) == -1)
        {
          fprintf(stderr, _("Failed to allocate memory.\n"));
          free(unquoted);
          free(dir);
          return NULL;
        }
      }
      else
        ret = xstrdup(name);
      if (isdir == 1) {
        rl_completion_append_character = '/';
      } else {
        rl_completion_append_character = ' ';
      }
      return ret;
    }
  }
  free(unquoted);
  free(dir);
  return NULL;
}

static char *variable_completion_function(const char *text, int state)
{
    static int list_index, len;
    char *name;

    if(!state) {
		list_index = 0;
		len = strlen(text);
    }

    while((name = setvariables[list_index].name) != 0) {
		list_index++;
		if(strncmp(name, text, len) == 0)
			return xstrdup(name);
    }
    return 0;
}

static char *ftplist_completion_function(const char *text, int state)
{
	static int len;
	static listitem *lip = 0;

	if(!state) {
		len = strlen(text);
		lip = gvFtpList->first;
	}

	while(lip) {
		char *name;
		Ftp *f = (Ftp *)lip->data;
		lip = lip->next;
		if(f->url == 0)
			/* Ftp not connected */
			continue;
		name = f->url->alias ? f->url->alias : f->url->hostname;
		if(strncmp(name, text, len) == 0)
			return backslash_quote(name);
	}
	return 0;
}

static char *taglist_completion_function(const char *text, int state)
{
	static int len;
	static listitem *lip = 0;

	if(!ftp->taglist)
		return 0;

	if(!state) {
		len = strlen(text);
		lip = ftp->taglist->first;
	}

	rl_filename_completion_desired = 1;
#ifndef HAVE_LIBEDIT
	rl_filename_quoting_desired = 1;
#endif

	while(lip) {
		rfile *f = (rfile *)lip->data;
		lip = lip->next;
		if(strncmp(f->path, text, len) == 0)
			return xstrdup(f->path);
	}
	return 0;
}

static char *local_taglist_completion_function(const char *text, int state)
{
	static int len;
	static listitem *lip = 0;

	if(!gvLocalTagList)
		return 0;

	if(!state) {
		len = strlen(text);
		lip = gvLocalTagList->first;
	}

	rl_filename_completion_desired = 1;
#ifndef HAVE_LIBEDIT
	rl_filename_quoting_desired = 1;
#endif

	while(lip) {
		char *p = (char *)lip->data;
		lip = lip->next;
		if(strncmp(p, text, len) == 0)
			return xstrdup(p); /* backslash_quote(p); */
	}
	return 0;
}

/* Attempt to complete on the contents of TEXT.  START and END bound the
 * region of rl_line_buffer that contains the word to complete.  TEXT is
 * the word to complete.  We can use the entire contents of rl_line_buffer
 * in case we want to do some simple parsing.  Return the array of matches,
 * or NULL if there aren't any.
 */
char **the_complete_function(char *text, int start, int end)
{
    char **matches = 0;
	cmd_t *cp;
	char *e, *c, *orgc;
	cpl_t cpl;
	char quoted;

	rl_completion_append_character = ' ';

	if(force_completion_type == cpUnset) {
		int i;
		int cmd_start;

		orgc = c = xstrdup(rl_line_buffer);

		if(c) {
			/* find the start index of the command to complete */
			i = start;
			while(c[i] != ';' && i>0)
				i--;
			cmd_start = i;
			if(c[i] == ';')
				cmd_start++;
		} else
			/* rl_line_buffer is empty, no command yet */
			cmd_start = start;

		/* is this an argument to a command? */
		if(start != cmd_start) {
			if(c[cmd_start] == '!')
				cpl = cpLocalFile;
			else {
				c += cmd_start;
				e = strqsep(&c, ' ');
        if (e == NULL)
          return NULL;
				cp = find_func(e, false);
				if(!cp) {
					free(orgc);
					return 0;
				}
				cpl = cp->cpltype;
			}
		} else
			cpl = cpCommand;
		free(orgc);
	} else
		cpl = force_completion_type;

	quoted = ((char_is_quoted(rl_line_buffer, start) &&
			   strchr(rl_completer_quote_characters, rl_line_buffer[start-1]))
			  ? rl_line_buffer[start-1] : 0);
	text = dequote_filename(text, quoted);

	remote_dir_only = false;
	switch(cpl) {
	  case cpUnset:
	  case cpNone:
		break;
	  case cpCommand:
		matches = rl_completion_matches(text, command_completion_function);
		break;
	  case cpLocalFile:
		matches = rl_completion_matches(text, rl_filename_completion_function);
		break;
	  case cpRemoteDir:
		remote_dir_only = true;
		/* fall through */
	  case cpRemoteFile:
		if(!gvRemoteCompletion)
			break;
		matches = rl_completion_matches(text, remote_completion_function);
		break;
	  case cpHostname:
		matches = rl_completion_matches(text, hostname_completion_function);
		break;
	  case cpBookmark:
		matches = rl_completion_matches(text, bookmark_completion_function);
		break;
	  case cpAlias:
		matches = rl_completion_matches(text, alias_completion_function);
		break;
	  case cpVariable:
		matches = rl_completion_matches(text, variable_completion_function);
		break;
	  case cpFtpList:
		matches = rl_completion_matches(text, ftplist_completion_function);
		break;
	  case cpTaglist:
		matches = rl_completion_matches(text, taglist_completion_function);
		break;
	  case cpLocalTaglist:
		matches = rl_completion_matches(text,
										local_taglist_completion_function);
		break;
	}
	free(text);
    return matches;
}

#endif /* HAVE_LIBREADLINE */
